﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright company="" file="Game.xaml.cs">
//   
// </copyright>
// <summary>
//   (c) Copyright Ascensio System Limited 2008-2009
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace corelib
{
    #region usings

    using System;
    using System.IO;
    using System.Threading;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Threading;
    using AHD.MyNes.Nes;
    using Sine;

    #endregion

    /// <summary>
    /// </summary>
    public partial class Game : UserControl
    {
        #region Constants

        /// <summary>
        /// </summary>
        private const int PreloadBufferSize = 1024;

        /// <summary>
        /// </summary>
        private const int ThreadStopTimeout = 1000;

        #endregion

        #region Members

        /// <summary>
        /// </summary>
        public static DependencyProperty romStreamPreloadProperty =
            DependencyProperty.Register("RomStreamPreload", 
                                        typeof (bool), 
                                        typeof (Game), 
                                        new PropertyMetadata(false));

        /// <summary>
        /// </summary>
        public static DependencyProperty useScale2XProperty = DependencyProperty.Register("UseScale2X", 
                                                                                          typeof (bool), 
                                                                                          typeof (Game), 
                                                                                          new PropertyMetadata
                                                                                              (true, Scale2xChanged));

        private static void Scale2xChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (((Game)d).emu != null)
            {
                ((Game) d).emu.Vid.X2Sampling = (bool) e.NewValue;
            }
        }

        /// <summary>
        /// </summary>
        public static DependencyProperty volumeProperty = DependencyProperty.Register("Volume", 
                                                                                      typeof (int), 
                                                                                      typeof (Game), 
                                                                                      new PropertyMetadata(
                                                                                          10, VolumeChanged));

        /// <summary>
        /// </summary>
        private NesEmulator emu;

        /// <summary>
        /// </summary>
        private Thread emulationThread;

        /// <summary>
        /// </summary>
        private State emuState;

        /// <summary>
        /// </summary>
        private FrameworkElement keyHandler;

        /// <summary>
        /// </summary>
        private NesMediaStreamSource sound;

        /// <summary>
        /// </summary>
        private int volume = 10;

        #endregion

        #region Constructor

        public event EventHandler Loaded;

        private void InvokeLoaded(EventArgs e)
        {
            EventHandler handler = Loaded;
            if (handler != null)
            {
                handler(this, e);
            }
        }

        /// <summary>
        /// </summary>
        public Game()
        {
            InitializeComponent();
        }

        public void ShowOpenFile() {
            OpenFileDialog dlg = new OpenFileDialog { Filter = "NES Roms (*.nes)|*.nes", Multiselect = false };
            if (dlg.ShowDialog().GetValueOrDefault(false))
            {
                RomStream = dlg.File.OpenRead();
                PreloadROM();
            }
        }

        #endregion

        #region Properties

        /// <summary>
        /// </summary>
        public Stream RomStream { get; set; }

        /// <summary>
        /// </summary>
        public bool RomStreamPreload
        {
            get { return (bool) GetValue(romStreamPreloadProperty); }

            set { SetValue(romStreamPreloadProperty, value); }
        }

        /// <summary>
        /// </summary>
        public bool UseScale2X
        {
            get { return (bool) GetValue(useScale2XProperty); }

            set { SetValue(useScale2XProperty, value); }
        }

        /// <summary>
        /// </summary>
        public int Volume
        {
            get { return (int) GetValue(volumeProperty); }

            set { SetValue(volumeProperty, value);SetVolume(value); }
        }

        #endregion

        #region Methods

        /// <summary>
        /// </summary>
        /// <exception cref="ArgumentException">
        /// </exception>
        public void Start()
        {
            if (RomStream == null)
            {
                throw new ArgumentException("RomStream isn't initialized");
            }

            if (emu != null)
            {
                emu.QuitEngine();
            }

            // Sound init
            InitSound();
            InitEmulator();

            // load tanks
            LoadROMImage();
            StartEmulationThread();
            InitKeyHandlers();
            if (FocusManager.GetFocusedElement() != this)
            {
                Focus();
            }
            isStarted = true;
        }

        /// <summary>
        /// </summary>
        /// <param name="stream">
        /// </param>
        public byte[] SaveGameState()
        {
            if (emuState != null)
            {
                Pause();
                byte[] data = emuState.SaveState();
                Resume();
                return data;
            }
            return null;
        }

        /// <summary>
        /// </summary>
        /// <param name="stream">
        /// </param>
        public void LoadGameState(byte[] data)
        {
            if (emuState != null)
            {
                Pause();
                emuState.LoadState(data);
                Resume();
            }
        }

        /// <summary>
        /// </summary>
        /// <param name="stream">
        /// </param>
        public void TakeScreenshot(Stream stream)
        {
            if (emu != null)
            {
                emu.Vid.TakeScreenShot(stream);
            }
        }


        public bool IsPaused { 
            get
            {
                if (emu != null)
                {
                    return emu.isPaused;
                }
                return false;
            } 
        }

        public bool IsStarted
        {
            get { return isStarted; }
        }

        /// <summary>
        /// </summary>
        public void Pause()
        {
            if (emu != null && !emu.isPaused)
            {
                emu.TogglePause();
                media.Pause();
            }
        }

        /// <summary>
        /// </summary>
        public void Resume()
        {
            if (emu != null && emu.isPaused)
            {
                emu.TogglePause();
                media.Play();
            }
        }

        /// <summary>
        /// </summary>
        /// <param name="key">
        /// </param>
        public void SignalKeyDown(Key key)
        {
            ProcessKey(key, true);
        }

        /// <summary>
        /// </summary>
        /// <param name="key">
        /// </param>
        public void SignalKeyUp(Key key)
        {
            ProcessKey(key, false);
        }

        #endregion

        #region Overrides

        /// <summary>
        /// </summary>
        /// <param name="e">
        /// </param>
        protected override void OnLostFocus(RoutedEventArgs e)
        {
            base.OnLostFocus(e);
            Focus();
        }

        /// <summary>
        /// </summary>
        /// <param name="e">
        /// </param>
        protected override void OnKeyDown(KeyEventArgs e)
        {
            base.OnKeyDown(e);
            Game_KeyDown(this, e);
        }

        /// <summary>
        /// </summary>
        /// <param name="e">
        /// </param>
        protected override void OnKeyUp(KeyEventArgs e)
        {
            base.OnKeyUp(e);
            Game_KeyUp(this, e);
        }

        #endregion

        #region Utility methods

        /// <summary>
        /// </summary>
        /// <param name="d">
        /// </param>
        /// <param name="e">
        /// </param>
        private static void VolumeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            Game game = (Game) d;
            game.SetVolume((int) e.NewValue/10);
        }

        /// <summary>
        /// </summary>
        /// <param name="volumeLevel">
        /// </param>
        private void SetVolume(int volumeLevel)
        {
            if (volumeLevel < 0)
            {
                volumeLevel = 0;
            }
            else if (volumeLevel > 10)
            {
                volumeLevel = 10;
            }

            volume = volumeLevel;
            
            if (emu != null)
            {
                emu.myapu.MasterVolume = volume;
                if (volume==0)
                {
                    emu.SoundEnabled = false;
                    emu.myapu.Pause();
                }
                else
                {
                    emu.SoundEnabled = true;
                    emu.myapu.Play();
                }
            }
        }

        /// <summary>
        /// </summary>
        private void InitKeyHandlers()
        {
            DependencyObject parent = Parent;
            do
            {
                FrameworkElement root = parent as FrameworkElement;
                if (root != null)
                {
                    if (root.Parent == null)
                    {
                        root.KeyDown += Game_KeyDown;
                        root.KeyUp += Game_KeyUp;
                        keyHandler = root;
                        break;
                    }

                    parent = root.Parent;
                }
            }
            while (true);
        }


        public static DependencyProperty FrameRateProperty = DependencyProperty.Register("FrameRate",typeof(int), typeof(Game), new PropertyMetadata(50, new PropertyChangedCallback(FrameRateChanged)));

        private static void FrameRateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            Game game = (Game) d;
            int fps = (int) e.NewValue;
            SetFps(fps, game);
        }

        private static void SetFps(int fps, Game game) {
            Application.Current.Host.Settings.MaxFrameRate = (int) fps;
            if (game.emu!=null)
            {
                //game.emu.FPS = (int) fps;
                //game.emu.Ticks_Per_Scanline = (uint)(106 * fps / 50.0); //pal
            }
        }

        public int FrameRate
        {
            get
            {
                return (int) GetValue(FrameRateProperty);
            }
            set
            {
                SetValue(FrameRateProperty, value);
            }
        }



        /// <summary>
        /// </summary>
        private void StartEmulationThread()
        {
            if (emulationThread != null && emulationThread.ThreadState == ThreadState.Running)
            {
                // End thread
                if (!emulationThread.Join(ThreadStopTimeout))
                {
                    emulationThread.Abort();
                }
            }

            Application.Current.Host.Settings.EnableFrameRateCounter = true;
            SetFps(FrameRate, this);
            emu.PreFrame();
            CompositionTarget.Rendering += new EventHandler(CompositionTarget_Rendering);
            emulationThread = new Thread(new ThreadStart(FrameTick));
            emulationThread.Start();
        }


        private AutoResetEvent renderTick = new AutoResetEvent(false);

        private void FrameTick()
        {
            while (true)
            {
                renderTick.WaitOne(20);//20 ms = 50 fps
                emu.DoFrame();

            }
        }

        void CompositionTarget_Rendering(object sender, EventArgs e)
        {
            if (emu!=null)
            {
                //emu.DoFrame();
                emu.Vid.Bmp.Invalidate();
                gameField.Source = emu.Vid.Bmp;
                sound.Drop();
                renderTick.Set();
               
            }
        }

        /// <summary>
        /// </summary>
        private void LoadROMImage()
        {
            if (RomStreamPreload)
            {
                PreloadROM();
            }

            emu.LoadCart(RomStream);
        }

        /// <summary>
        /// </summary>
        public void PreloadROM()
        {
            MemoryStream preload = new MemoryStream();
            byte[] buffer = new byte[PreloadBufferSize];
            int readed = 0;
            while ((readed = RomStream.Read(buffer, 0, buffer.Length)) > 0)
            {
                preload.Write(buffer, 0, readed);
            }

            preload.Position = 0;
            RomStream = preload;
            InvokeLoaded(null);
        }

        /// <summary>
        /// </summary>
        private void InitEmulator()
        {
            emu = new NesEmulator(gameField, sound);
            emu.InitializeEngine();
            emu.Vid.X2Sampling = UseScale2X;
            emu.CurrentSystem = NesSystem.PAL;

            emu.SpeedThrottling = true;

            // Video options
            emu.Vid.DrawBit = DrawBits.Bit_32;
            emu.Vid.Size = DrawSize.X1;
            emu.Vid.Filter = DrawFilter.NearestNeighbor;

            // Colors
            emu.Vid.Alpha = 150;
            emu.Vid.BlueMask = 31;
            emu.Vid.GreenMask = 2016;
            emu.Vid.RedMask = 63488;
            emuState = new State(emu);
            SetVolume(10); // TODO: From settings
        }

        /// <summary>
        /// </summary>
        private void InitSound()
        {
            sound = new NesMediaStreamSource(audioStream);
            //sound.PlayStart += SoundPlayStart;
            //sound.PlayStop += SoundPlayStop;
            media.SetSource(sound);
            media.Play();
        }

        /// <summary>
        /// </summary>
        /// <param name="sender">
        /// </param>
        /// <param name="e">
        /// </param>
        private void SoundPlayStop(object sender, EventArgs e)
        {
            Dispatcher.BeginInvoke(() => media.Stop());
        }

        /// <summary>
        /// </summary>
        /// <param name="sender">
        /// </param>
        /// <param name="e">
        /// </param>
        private void SoundPlayStart(object sender, EventArgs e)
        {
            Dispatcher.BeginInvoke(() => media.Play());
        }

        /// <summary>
        /// </summary>
        /// <param name="sender">
        /// </param>
        /// <param name="e">
        /// </param>
        private void Game_KeyUp(object sender, KeyEventArgs e)
        {
            ProcessKey(e.Key, false);
            e.Handled = true;
        }

        /// <summary>
        /// </summary>
        /// <param name="sender">
        /// </param>
        /// <param name="e">
        /// </param>
        private void Game_KeyDown(object sender, KeyEventArgs e)
        {
            ProcessKey(e.Key, true);
            e.Handled = true;
        }

        /// <summary>
        /// </summary>
        /// <param name="key">
        /// </param>
        /// <param name="set">
        /// </param>
        private void ProcessKey(Key key, bool set)
        {
            // TODO: Load settings
            if (emu != null)
            {
                if (key == Key.Left)
                {
                    emu.myJoypad.Left = set;
                }

                if (key == Key.Right)
                {
                    emu.myJoypad.Right = set;
                }

                if (key == Key.Up)
                {
                    emu.myJoypad.Up = set;
                }

                if (key == Key.Down)
                {
                    emu.myJoypad.Down = set;
                }

                if (key == Key.Z)
                {
                    emu.myJoypad.A = set;
                }

                if (key == Key.X)
                {
                    emu.myJoypad.B = set;
                }

                if (key == Key.Space)
                {
                    emu.myJoypad.Select = set;
                }

                if (key == Key.Enter)
                {
                    emu.myJoypad.Start = set;
                }
                if (!set)
                {
                    if (IsStarted)
                    {
                        if (key == Key.F5)
                        {
                            saveData = SaveGameState();
                            InvokeGameSaved(saveData);
                            GameMessage(MessageType.Info, "Game saved");
                        }
                        if (key == Key.F9)
                        {
                            if (saveData != null)
                            {
                                LoadGameState(saveData);
                                GameMessage(MessageType.Info, "Game loaded");
                                InvokeGameRestored(saveData);
                            }
                            else
                            {
                                GameMessage(MessageType.Error, "No save exists");
                            }
                        }
                    }
                    else
                    {
                        GameMessage(MessageType.Warning, "Cannot save not started game");
                    }

                }
            }
        }

        public delegate void GameSavedDelegate(byte[] stream);
        public delegate void GameRestoredDelegate(byte[] stream);
        public event GameSavedDelegate GameSaved = null;
        public event GameRestoredDelegate GameRestored = null;

        private void InvokeGameRestored(byte[] stream)
        {
            GameRestoredDelegate restored = GameRestored;
            if (restored != null)
            {
                restored(stream);
            }
        }

        private void InvokeGameSaved(byte[] stream)
        {
            GameSavedDelegate dlg = GameSaved;
            if (dlg != null)
            {
                dlg(stream);
            }
        }

        public delegate void GameMessageDelegate(MessageType type, string message);

        public event GameMessageDelegate OnGameMessage = null;

        private void GameMessage(MessageType type, string message)
        {
            GameMessageDelegate dlg = OnGameMessage;
            if (dlg != null)
            {
                dlg(type, message);
            }
        }

        private byte[] saveData;
        private bool isStarted;

        public enum MessageType
        {
            Info,
            Error,
            Warning
        }

        #endregion

        private Stream audioStream = null;

        public void SaveSound()
        {
            SaveFileDialog sfd = new SaveFileDialog();
            sfd.DefaultExt = ".wav";
            if ((bool) sfd.ShowDialog())
            {
                audioStream = sfd.OpenFile();
            }
        }
    }
}